﻿namespace EFS.RC.CC.Tools.AVR
{
	/// <summary>
	/// StepByStep palyer
	/// </summary>
	internal abstract class AStepByStep : EFS.Common.Async.AsyncRoot
	{
		#region Composition

		/// <summary>
		/// Step item
		/// </summary>
		internal class Step
		{
			// File name to play
			public System.String PlayFileName = null;

			// Tone collected
			public System.Int32? ToneReceived = null;

			// flago to collect tone
			public System.Boolean WaitTone = false;
		}

		// sync access
		private object syncAccess = new object();

		/// Conference participant
		private EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant = null;

		// Steps
		private EFS.RC.CC.Tools.AVR.AStepByStep.Step[] steps = null;

		// Current intem index
		private System.Int32 currentStepIndex = -1;

		/// <summary>
		/// Current audio player
		/// </summary>
		private EFS.RC.CC.Tools.Players.Audio audioPlayer = null;

		/// <summary>
		/// Status
		/// </summary>
		public enum StepByStepStatus { Idle, Started, Terminated }

		// Status
		private StepByStepStatus status = StepByStepStatus.Idle;

		#endregion

		#region Delegates

		/// <summary>
		/// Asynk terminate 
		/// </summary>
		public EFS.Common.Async.AsyncDelegate<EFS.RC.CC.Tools.AVR.AStepByStep> AsinkTerminatedDelegate { get; private set; }

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="officeServiceSessionParticipant"></param>
		/// <param name="steps"></param>
		public AStepByStep(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant, EFS.RC.CC.Tools.AVR.AStepByStep.Step[] steps)
		{
			// log
			EFS.EventLog.Log.LogInfo("EFS.RC.CC.Tools.Players:Audio:ctor");

			// check
			EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");
			EFS.Common.Verify.Array(steps, "steps");

			// delegate
			AsinkTerminatedDelegate = new EFS.Common.Async.AsyncDelegate<EFS.RC.CC.Tools.AVR.AStepByStep>(this);

			// set
			this.officeServiceSessionParticipant = officeServiceSessionParticipant;
			this.steps = steps;

			// register for terminated events
			officeServiceSessionParticipant.AsinkTerminatedDelegate.Register(OnOfficeServiceConferenceParticipantTerminated);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(officeServiceSessionParticipant.OfficeServiceConference.CustomerSession.ConversationGuid, "Audio:ctor", this);

			// log
			EFS.EventLog.Log.LogInfo("SupervisorContext", this);
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this object into a nice formated string
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format("EFS.RC.CC.Tools.AVR.StepByStep [{0}]", officeServiceSessionParticipant.OfficeServiceConference.Describe());
		}

		/// <summary>
		/// Begin startup
		/// </summary>
		public EFS.Common.Async.AsyncProcess BeginStartup()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartup", this);

			// start
			return BeginAsyncProcess2(Startup, OnStartupComplete);
		}

		/// <summary>
		/// Begin startup
		/// </summary>
		public EFS.Common.Async.AsyncProcess BeginShutdown()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartup", this);

			// start
			return BeginAsyncProcess2(Shutdown, OnShutdownComplete);
		}

		// Status
		public StepByStepStatus Status
		{
			get { return status; }
		}

		/// <summary>
		/// Tone received 
		/// </summary>
		/// <param name="tone"></param>
		/// <param name="volume"></param>
		public void HandleTone(System.Int32 tone, System.Single volume)
		{
			lock (syncAccess)
			{
				// check status
				if (status != StepByStepStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// check player
				if (null != audioPlayer)
				{
 					// do not record tone as player not completed
					return;
				}

				// get current item
				EFS.RC.CC.Tools.AVR.AStepByStep.Step currentStep = CurrentStep;

				// call up
				if (ChechReceivedTone(currentStepIndex, tone))
				{
					// keep value
					currentStep.ToneReceived = tone;

					// log answer
					EFS.RC.CC.Tools.Loggers.CHL.Add(officeServiceSessionParticipant.OfficeServiceConference.CustomerSession.ConversationGuid, currentStepIndex + 1, tone); 

					// next
					BeginAsyncProcess2(NextStep);
				}
				else
				{
					// close
					BeginAsyncProcess2(PlayCurrentStep);
				}
			}
		}

		#endregion

		#region Startup

		/// <summary>
		/// Startup
		/// </summary>
		private void Startup()
		{
			// log
			EFS.EventLog.Log.LogInfo("Startup", this);

			// sync access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				if (status != StepByStepStatus.Idle)
				{
					throw new System.InvalidOperationException("Only Idle StepByStep can be started");
				}

				// started
				status = StepByStepStatus.Started;
			}

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(officeServiceSessionParticipant.OfficeServiceConference.CustomerSession.ConversationGuid, "Startup", this);

			// join
			BeginAsyncProcess2(NextStep);
		}

		/// <summary>
		/// Start-up complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnStartupComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnStartupComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				EFS.EventLog.Log.LogInfo("OnStartupComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// log
				HandleExceptionSafe(ex);
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// On step completed
		/// </summary>
		/// <param name="currentStepIndex"></param>
		/// <param name="tone"></param>
		/// <returns></returns>
		protected abstract System.Boolean ChechReceivedTone(System.Int32 currentStepIndex, System.Int32? tone);

		/// <summary>
		/// Increment step
		/// </summary>
		/// <returns></returns>
		private System.Boolean IncrementStep()
		{
			// next ste
			System.Int32 nextStepIndex = currentStepIndex + 1;

			// check
			if (nextStepIndex >= steps.Length)
			{
				// done
				return false;
			}

			// set next index
			currentStepIndex = nextStepIndex;

			// done
			return true;
		}

		/// <summary>
		/// Get current step
		/// </summary>
		private EFS.RC.CC.Tools.AVR.AStepByStep.Step CurrentStep
		{
			get
			{
				// check
				if (currentStepIndex < 0 || currentStepIndex >= steps.Length)
				{
					throw new System.InvalidOperationException("Step Index outside of array limits");
				}

				// get current item
				EFS.RC.CC.Tools.AVR.AStepByStep.Step currentStep = steps[currentStepIndex];

				// check
				EFS.Common.Verify.Reference(currentStep, "item");

				// done
				return currentStep;
			}
		}

		/// <summary>
		/// Unhandled exception
		/// </summary>
		/// <param name="ex"></param>
		protected override void OnException(System.Exception ex)
		{
			EFS.EventLog.Log.LogInfo("OnException", this);

			// check
			EFS.Common.Verify.Reference(ex, "ex");

			// log
			EFS.EventLog.Log.LogException(ex, this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(officeServiceSessionParticipant.OfficeServiceConference.CustomerSession.ConversationGuid, "OnException", ex, this);

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Audio session terminated
		/// </summary>
		/// <param name="audioPlayer"></param>
		private void OnAudioPlayerTerminated(EFS.RC.CC.Tools.Players.Audio audioPlayer)
		{
			EFS.EventLog.Log.LogInfo("OnOfficeServiceConferenceTerminated", this);

			// check
			EFS.Common.Verify.Reference(audioPlayer, "audioPlayer");

			// remove current player
			this.audioPlayer = null;

			// get current item
			EFS.RC.CC.Tools.AVR.AStepByStep.Step currentStep = CurrentStep;

			// check if we need to wait tone 
			if (!currentStep.WaitTone)
			{
				// next
				BeginAsyncProcess2(NextStep);
			}
		}

		/// <summary>
		/// Customer session terminated
		/// </summary>
		/// <param name="playerHunter"></param>
		private void OnOfficeServiceConferenceParticipantTerminated(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant)
		{
			EFS.EventLog.Log.LogInfo("OnOfficeServiceConferenceTerminated", this);

			// check
			EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");

			// check mine
			EFS.Common.Verify.Condition(this.officeServiceSessionParticipant.Equals(officeServiceSessionParticipant), "Terminated OfficeService Conferece Participant is different");

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Next step
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void NextStep()
		{
			EFS.EventLog.Log.LogInfo("OnAudioVideoPlayerStateChanged", this);

			// sync access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());

			lock (syncAccess)
			{
				// check status
				if (status != StepByStepStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// check
				if (!IncrementStep())
				{
					// shutdown
					BeginShutdown();
				}
				else
				{
					// play current
					PlayCurrentStep();
				}
			}
		}

		/// <summary>
		/// Next step
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void PlayCurrentStep()
		{
			EFS.EventLog.Log.LogInfo("OnAudioVideoPlayerStateChanged", this);

			// sync access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());

			lock (syncAccess)
			{
				// check status
				if (status != StepByStepStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// get current item
				EFS.RC.CC.Tools.AVR.AStepByStep.Step currentStep = CurrentStep;

				// make new player
				audioPlayer = new EFS.RC.CC.Tools.Players.Audio(officeServiceSessionParticipant, currentStep.PlayFileName, false);
				audioPlayer.AsinkTerminatedDelegate.Register(OnAudioPlayerTerminated);

				// play
				audioPlayer.BeginStartup();
			}
		}

		#endregion

		#region Shutdown

		/// <summary>
		/// Shut down
		/// </summary>
		public void Shutdown()
		{
			EFS.EventLog.Log.LogInfo("Shutdown", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check already terminated
				if (StepByStepStatus.Terminated == status)
				{
					// already terminated
					return;
				}

				// set terinated
				status = StepByStepStatus.Terminated;
			}

			// notify terminate
			AsinkTerminatedDelegate.Start();
		}

		/// <summary>
		/// Shut down complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnShutdownComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnShutdownComplete", this);

			try
			{
				// check
				ap.Throw();
			}
			catch (System.Exception ex)
			{
				// log
				EFS.EventLog.Log.LogException(ex, this);
			}
		}

		#endregion
	}
}
